// ShadowVolume10.fx
// Copyright (c) 2005 Microsoft Corporation. All rights reserved.
//

#include "ShaderHelp.fxh"

#define LIGHT_FALLOFF 1.2f
#define NOVERTEX 0xfffffffe
#define EXTRUDE_AMT 1000.0f
#define EXTRUDE_BIAS 0.1f

cbuffer cbPerFrame
{
	Light gLight;
	float3 gLightPos;
	float3 gLightDir;
	float4 gLightAmbient;
	float4 gLightDiffuse;
	float4 gLightSpec;
	float3 gEyePosW;
	float4 gShadowColor;
};

cbuffer cbPerObject
{
	matrix gWorld;
	matrix gWVP;
	matrix gTexMtx;
	matrix gViewProj;
};

Texture2D gDiffuseMap;
Texture2D gNormalMap;
Texture2D gSpecMap;

struct VSSceneIn
{
    float3 posL          : POSITION;    
    float3 normL         : NORMAL;  
    float2 texC          : TEXCOORD;
    float3 tanL			: TANGENT;
};

struct GSShadowIn
{
    float3 pos          : POS; 
    float3 norm         : TEXCOORD;
};

struct PSShadowIn
{
    float4 pos			: SV_Position;
};

struct PSSceneIn
{
    float4 posH : SV_Position;
    float3 posW : POSITION;
    float2 texC : TEXCOORD;
    float3 normW : NORMAL;
    float3 tanW : TANGENT;
};

SamplerState gTriLinearSam
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

DepthStencilState DisableDepth
{
    DepthEnable = FALSE;
    DepthWriteMask = ZERO;
};

DepthStencilState EnableDepth
{
    DepthEnable = TRUE;
    DepthWriteMask = ALL;
};

DepthStencilState TwoSidedStencil
{
    DepthEnable = true;
    DepthWriteMask = ZERO;
    DepthFunc = Less;
    
    // Setup stencil states
    StencilEnable = true;
    StencilReadMask = 0xFFFFFFFF;
    StencilWriteMask = 0xFFFFFFFF;
    
    BackFaceStencilFunc = Always;
    BackFaceStencilDepthFail = Incr;
    BackFaceStencilPass = Keep;
    BackFaceStencilFail = Keep;
    
    FrontFaceStencilFunc = Always;
    FrontFaceStencilDepthFail = Decr;
    FrontFaceStencilPass = Keep;
    FrontFaceStencilFail = Keep;
};

DepthStencilState RenderNonShadows
{
    DepthEnable = true;
    DepthWriteMask = ZERO;
    DepthFunc = Less_Equal;
    
    StencilEnable = true;
    StencilReadMask = 0xFFFFFFFF;
    StencilWriteMask = 0x0;
    
    FrontFaceStencilFunc = Equal;
    FrontFaceStencilPass = Keep;
    FrontFaceStencilFail = Zero;
    
    BackFaceStencilFunc = Never;
    BackFaceStencilPass = Zero;
    BackFaceStencilFail = Zero;
};

DepthStencilState TwoSidedStencilRev
{
    DepthEnable = true;
    DepthWriteMask = ZERO;
    DepthFunc = Less;
    
    // Setup stencil states
    StencilEnable = true;
    StencilReadMask = 0xFFFFFFFF;
    StencilWriteMask = 0xFFFFFFFF;
    
    BackFaceStencilFunc = Always;
    BackFaceStencilDepthFail = Decr;
    BackFaceStencilPass = Keep;
    BackFaceStencilFail = Keep;
    
    FrontFaceStencilFunc = Always;
    FrontFaceStencilDepthFail = Incr;
    FrontFaceStencilPass = Keep;
    FrontFaceStencilFail = Keep;
};

DepthStencilState RenderNonShadowsRev
{
    DepthEnable = true;
    DepthWriteMask = ZERO;
    DepthFunc = Less_Equal;
    
    StencilEnable = true;
    StencilReadMask = 0xFFFFFFFF;
    StencilWriteMask = 0x0;
    
    BackFaceStencilFunc = Equal;
    BackFaceStencilPass = Keep;
    BackFaceStencilFail = Zero;
    
    FrontFaceStencilFunc = Never;
    FrontFaceStencilPass = Zero;
    FrontFaceStencilFail = Zero;
};

BlendState DisableFrameBuffer
{
    BlendEnable[0] = FALSE;
    RenderTargetWriteMask[0] = 0x0;
};

BlendState EnableFrameBuffer
{
    BlendEnable[0] = FALSE;
    RenderTargetWriteMask[0] = 0x0F;
};

BlendState NoBlending
{
    AlphaToCoverageEnable = FALSE;
    BlendEnable[0] = FALSE;
    RenderTargetWriteMask[0] = 0x0F;
};

BlendState AdditiveBlending
{
    AlphaToCoverageEnable = FALSE;
    BlendEnable[0] = TRUE;
    SrcBlend = ONE;
    DestBlend = ONE;
    BlendOp = ADD;
    SrcBlendAlpha = ZERO;
    DestBlendAlpha = ZERO;
    BlendOpAlpha = ADD;
    RenderTargetWriteMask[0] = 0x0F;
};

BlendState SrcAlphaBlending
{
    AlphaToCoverageEnable = FALSE;
    BlendEnable[0] = TRUE;
    SrcBlend = SRC_ALPHA;
    DestBlend = INV_SRC_ALPHA;
    BlendOp = ADD;
    SrcBlendAlpha = ZERO;
    DestBlendAlpha = ZERO;
    BlendOpAlpha = ADD;
    RenderTargetWriteMask[0] = 0x0F;
};

RasterizerState DisableCulling
{
    CullMode = NONE;
};

RasterizerState EnableCulling
{
    CullMode = BACK;
};

RasterizerState EnableCullingRev
{
    CullMode = FRONT;
};


//
// VS for rendering basic textured and lit objects
//
PSSceneIn VSScenemain( VSSceneIn input )
{
    PSSceneIn output = (PSSceneIn)0.0;

    //output our final position in clipspace
    output.posH = mul( float4( input.posL, 1 ), gWVP );
    output.posW = mul(float4( input.posL, 1 ), gWorld );
    output.normW = mul( float4( input.normL, 1 ), gWorld );
    output.tanW = mul( float4( input.tanL, 1 ), gWorld );
    
    output.texC = mul(float4(input.texC, 0.0f, 1.0f), gTexMtx);
    
    return output;
}

//
// VS for sending information to the shadow GS
//
GSShadowIn VSShadowmain( VSSceneIn input )
{
    GSShadowIn output = (GSShadowIn)0.0;

    //output our position in world space
    float4 pos = mul( float4(input.posL,1), gWorld );
    output.pos = pos.xyz;
    
    //world space normal
    output.norm = mul( input.normL, (float3x3)gWorld );
    
    return output;
}

//
// Helper to detect a silhouette edge and extrude a volume from it
//
void DetectAndProcessSilhouette( float3 N,         // Un-normalized triangle normal
                                 GSShadowIn v1,    // Shared vertex
                                 GSShadowIn v2,    // Shared vertex
                                 GSShadowIn vAdj,  // Adjacent triangle vertex
                                 inout TriangleStream<PSShadowIn> ShadowTriangleStream // triangle stream
                                 )
{    
    float3 NAdj = cross( v2.pos - vAdj.pos, v1.pos - vAdj.pos );
    
    float3 outpos[4];
    float3 extrude1 = normalize(v1.pos - gLightPos);
    float3 extrude2 = normalize(v2.pos - gLightPos);
        
    outpos[0] = v1.pos + EXTRUDE_BIAS*extrude1;
    outpos[1] = v1.pos + EXTRUDE_AMT*extrude1;
    outpos[2] = v2.pos + EXTRUDE_BIAS*extrude2;
    outpos[3] = v2.pos + EXTRUDE_AMT*extrude2;
        
    // Extrude silhouette to create two new triangles
    PSShadowIn Out;
    for(int v=0; v<4; v++)
    {
        Out.pos = mul( float4(outpos[v],1), gViewProj );
        ShadowTriangleStream.Append( Out );
    }
    ShadowTriangleStream.RestartStrip();
}

//
// GS for generating shadow volumes
//
[maxvertexcount(18)]
void GSShadowmain( triangleadj GSShadowIn In[6], inout TriangleStream<PSShadowIn> ShadowTriangleStream )
{
    // Compute un-normalized triangle normal
    float3 N = cross( In[2].pos - In[0].pos, In[4].pos - In[0].pos );
    
    // Compute direction from this triangle to the light
    float3 lightDir = gLightPos - In[0].pos;
    
    //if we're facing the light
    if( dot(N, lightDir) > 0.0f )
    {
        // For each edge of the triangle, determine if it is a silhouette edge
        DetectAndProcessSilhouette( lightDir, In[0], In[2], In[1], ShadowTriangleStream );
        DetectAndProcessSilhouette( lightDir, In[2], In[4], In[3], ShadowTriangleStream );
        DetectAndProcessSilhouette( lightDir, In[4], In[0], In[5], ShadowTriangleStream );
        
        //near cap
        PSShadowIn Out;
        for(int v = 0; v < 6; v += 2)
        {
            float3 extrude = normalize(In[v].pos - gLightPos);
            
            float3 pos = In[v].pos + EXTRUDE_BIAS*extrude;
            Out.pos = mul( float4(pos,1), gViewProj );
            ShadowTriangleStream.Append( Out );
        }
        ShadowTriangleStream.RestartStrip();
        
        //far cap (reverse the order)
        for(int y = 4; y >= 0; y -= 2)
        {
            float3 extrude = normalize(In[y].pos - gLightPos);
        
            float3 pos = In[y].pos + EXTRUDE_AMT*extrude;
            Out.pos = mul( float4(pos,1),gViewProj);
            ShadowTriangleStream.Append( Out );
        }
        ShadowTriangleStream.RestartStrip();
    }
}

//
// PS for rendering lit and textured triangles
//
float4 PSScenemain(PSSceneIn input) : SV_Target
{   
    float4 diffuse = gDiffuseMap.Sample( gTriLinearSam, input.texC );
    clip(diffuse.a - 0.15f);
    
    float4 spec = gSpecMap.Sample(gTriLinearSam, input.texC);
		
    float3 normalT = gNormalMap.Sample(gTriLinearSam, input.texC);
    
    spec.a *= 256.0f;
    
    normalT = 2.0f * normalT - 1.0f;
    
    float3 N = normalize(input.normW);
    float3 T = normalize(input.tanW - dot(input.tanW, N) * N);
    float3 B = cross(N,T);
    
    float3x3 TBN = float3x3(T, B, N);
    
    float3 bumpedNormalW = normalize(mul(normalT, TBN));
	
	SurfaceInfo v = {input.posW, bumpedNormalW, diffuse, spec};
	
    Light light = {gLightPos, -gLightDir, gLightAmbient, gLightDiffuse, gLightSpec, float3(0,0,0), 0, 0};
    float3 litColor = ParallelLight(v, light, gEyePosW);
    
    return float4(litColor, 1.0f);
}

//
// PS for rendering ambient scene
//
float4 PSAmbientmain(PSSceneIn input) : SV_Target
{   
    float4 diffuse = gDiffuseMap.Sample( gTriLinearSam, input.texC );
    clip(diffuse.a - 0.15f);
    
    float4 spec = gSpecMap.Sample(gTriLinearSam, input.texC);
    float3 normalT = gNormalMap.Sample(gTriLinearSam, input.texC);
    
    spec.a *= 256.0f;
    
    normalT = 2.0f * normalT - 1.0f;
    
    float3 N = normalize(input.normW);
    float3 T = normalize(input.tanW - dot(input.tanW, N) * N);
    float3 B = cross(N,T);
    
    float3x3 TBN = float3x3(T, B, N);
    
    float3 bumpedNormalW = normalize(mul(normalT, TBN));
    
    SurfaceInfo v = {input.posW, bumpedNormalW, diffuse, spec};
    Light light = {gLightPos, gLightDir, gLightAmbient, gLightDiffuse, gLightSpec, float3(0,0,0), 0, 0};
    float3 litColor = ParallelLight(v, light, gEyePosW) * gLightAmbient;
    
    return float4(litColor, 1.0f);
}

//
// PS for rendering shadow scene
//
float4 PSShadowmain(PSShadowIn input) : SV_Target
{   
    return float4( gShadowColor.xyz, 0.1f );
}

//
// RenderSceneLit - renders textured primitives
//
technique10 RenderSceneLit
{
    pass p0
    {
        SetVertexShader( CompileShader( vs_4_0, VSScenemain() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PSScenemain() ) );
        
        SetBlendState( AdditiveBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( RenderNonShadows, 0 ); //state, stencilref
        SetRasterizerState( EnableCulling );
    }  
}

//
// RenderShadow - extrudes shadows from geometry
//
technique10 RenderShadow
{
    pass p0
    {
        SetVertexShader( CompileShader( vs_4_0, VSShadowmain() ) );
        SetGeometryShader( CompileShader( gs_4_0, GSShadowmain() ) );
        SetPixelShader( CompileShader( ps_4_0, PSShadowmain() ) );
        
        SetBlendState( DisableFrameBuffer, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( TwoSidedStencil, 1 ); //state, stencilref
        SetRasterizerState( DisableCulling );
    }  
}

//
// RenderSceneAmbient - renders the scene with ambient lighting
technique10 RenderSceneAmbient
{
    pass p0
    {
        SetVertexShader( CompileShader( vs_4_0, VSScenemain() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PSAmbientmain() ) );
        
        SetBlendState( NoBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( EnableDepth, 0 );
        SetRasterizerState( EnableCulling );
    }  
}

//
// RenderSceneLit - renders textured primitives
//
technique10 RenderSceneLitRev
{
    pass p0
    {
        SetVertexShader( CompileShader( vs_4_0, VSScenemain() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PSScenemain() ) );
        
        SetBlendState( AdditiveBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( RenderNonShadowsRev, 0 ); //state, stencilref
        SetRasterizerState( EnableCullingRev );
    }  
}

//
// RenderShadow - extrudes shadows from geometry
//
technique10 RenderShadowRev
{
    pass p0
    {
        SetVertexShader( CompileShader( vs_4_0, VSShadowmain() ) );
        SetGeometryShader( CompileShader( gs_4_0, GSShadowmain() ) );
        SetPixelShader( CompileShader( ps_4_0, PSShadowmain() ) );
        
        SetBlendState( DisableFrameBuffer, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( TwoSidedStencilRev, 1 ); //state, stencilref
        SetRasterizerState( DisableCulling );
    }  
}

//
// RenderSceneAmbient - renders the scene with ambient lighting
technique10 RenderSceneAmbientRev
{
    pass p0
    {
        SetVertexShader( CompileShader( vs_4_0, VSScenemain() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PSAmbientmain() ) );
        
        SetBlendState( NoBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
        SetDepthStencilState( EnableDepth, 0 );
        SetRasterizerState( EnableCullingRev );
    }  
}